/* GLEDD - The GNU Light Emitting Diode DIRVER
 *
 * led.c
 *
 * Copyright (C) 2023 Chen <zggzcgy@163.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <asm/io.h>

// 模块名称
#define MODULE_NAME		"led"
// BCM2711芯片外设地址偏移
#define BCM2711_PERI_BASE	0xFE000000
// 虚拟内存偏移地址
#define GPIO_BASE_OFFSET	0x00200000
// 映射寄存器大小
#define GPIO_REGION_SIZE	0x3C
// 寄存器[模式]偏移量
#define GPIO_FSEL1_OFFSET	0x04
// 寄存器[操作]偏移量
#define GPIO_SET0_OFFSET	0x1C
// 寄存器[清除]偏移量
#define GPIO_CLR0_OFFSET	0x28

// 树莓派针脚编号
static int gpio_pin_num = 27;
// 树莓派针脚偏移
static int func_select_reg_offset;
static int func_output_reg_offset;
// 虚拟内存映射地址
static void* gpio_map = 0;

static int __init ModuleInit(void) {
	int val;
	// 获取虚拟内存地址
	gpio_map = ioremap(BCM2711_PERI_BASE + GPIO_BASE_OFFSET, GPIO_REGION_SIZE);

	// 计算[模式选择]寄存器偏移量
        func_select_reg_offset = GPIO_FSEL1_OFFSET * (gpio_pin_num /10);
	// 计算[输出模式]寄存器偏移量
	func_output_reg_offset = (gpio_pin_num % 10) * 3;

	// 读取32位寄存器数值
	val = ioread32(gpio_map + func_select_reg_offset);
	// 将数值中模式选择位设为0x3,即模式设置为输出模式
	val &= ~(7 << func_output_reg_offset);
	val |= 1 << func_output_reg_offset;
	// 设置针脚为输出模式
	iowrite32(val, gpio_map + func_select_reg_offset);
	// 设置寄存器为1，即电平拉高
	iowrite32(1 << gpio_pin_num, gpio_map + GPIO_SET0_OFFSET);
	pr_info("%s: Module load\n", MODULE_NAME);
	return 0;
}

static void __exit ModuleExit(void) {
	// 设置针脚寄存器为1，即电平拉高
	iowrite32(1 << gpio_pin_num, gpio_map + GPIO_CLR0_OFFSET);
	// 释放虚拟内存映射
	iounmap(gpio_map);
	pr_info("%s: Module unloaded\n", MODULE_NAME);
}

module_init(ModuleInit);
module_exit(ModuleExit);

MODULE_AUTHOR("CGY <zggzcgy@163.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS(MODULE_NAME);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Led module for RPI 4B");

// vim: tabstop=4 autoindent cindent
